package main

import (
	"fmt"
	"os"
	"sync"
	"time"
)

func input(nums ...int) <-chan int {
	c := make(chan int)
	go func() {
		for _, n := range nums {
			c <- n
		}
		close(c)
	}()
	return c
}

func square(in <-chan int) <-chan int {
	c := make(chan int)
	go func() {
		for n := range in {
			c <- n * n
		}
		close(c)
	}()
	return c
}

// https://stackoverflow.com/questions/19992334/how-to-listen-to-n-channels-dynamic-select-statement
// using reflect.Select for merge channels
func merge(cs ...<-chan int) <-chan int {
	o := make(chan int)

	var wg sync.WaitGroup
	wg.Add(len(cs))

	for _, c := range cs {
		go func(c <-chan int) {
			for n := range c {
				o <- n
			}
			wg.Done()
		}(c)
	}

	go func() {
		wg.Wait()
		close(o)
	}()

	return o
}

func main() {
	for n := range square(input(1, 2, 3)) {
		fmt.Println(n)
	}

	for n := range merge(square(input(1)), square(input(2))) {
		fmt.Println(n)
	}

	c := make(chan int, 10)
	go func() {
		c <- 1
		c <- 1
		c <- 1
		close(c)
	}()

	time.Sleep(time.Second)

	for v := range c {
		fmt.Println("-", v)
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////
///////////
// How to graceful close channle
// Don't close a channel from the receiver side
// Don't close a channel if the channel has multiple concurrent senders

func init() {
	gracefulClose1()
	gracefulClose2()
	gracefulClose3()

	mchannel()

	os.Exit(0)
}

// 1 sender -> m receiver
func gracefulClose1() {
	fmt.Println("gracefulClose1")
	g := sync.WaitGroup{}
	c := make(chan int, 10)
	// 1 sender
	go func() {
		for i := 0; i < 3; i++ {
			c <- i
		}
		close(c)
	}()

	// m receiver
	for i := 0; i < 5; i++ {
		g.Add(1)
		go func() {
			for v := range c {
				fmt.Println("receive: ", v)
				time.Sleep(time.Second)
			}
			g.Done()
		}()
	}
	g.Wait()
}

// m sender -> 1 receiver
func gracefulClose2() {
	fmt.Println("gracefulClose2")
	g := sync.WaitGroup{}
	c := make(chan int, 5)
	done := make(chan struct{})

	// m sender
	for i := 0; i < 5; i++ {
		g.Add(1)
		go func(s int) {
			defer g.Done()
			for j := s; j < 100; j++ {
				// quick close
				// select {
				// case <-done:
				//     return
				// default:
				// }

				select {
				case <-done:
					return
				case c <- j:
				}
			}
		}(i)
	}

	// 1 receiver
	go func() {
		for i := 0; i < 3; i++ {
			v := <-c
			fmt.Println("receive: ", v)
			time.Sleep(time.Second)
		}

		// notify all done waiter
		// if done <- struct{}{} just notify one waiter
		close(done)
	}()

	// channel c will be eventually garbage collected if no goroutines reference it any more

	g.Wait()
}

// m sender -> m receiver
func gracefulClose3() {
	fmt.Println("gracefulClose3")
	g := sync.WaitGroup{}
	c := make(chan int, 6)
	done := make(chan struct{})
	monitor := make(chan struct{})

	// monitor
	go func() {
		<-monitor
		fmt.Println("close")
		close(done)
	}()

	time.Sleep(time.Second)

	// m sender
	for i := 0; i < 5; i++ {
		g.Add(1)
		go func(s int) {
			defer g.Done()
			for j := 0; j < 2; j++ {
				select {
				case <-done:
					return
				case c <- j:
				}
			}

			select {
			case monitor <- struct{}{}:
			default:
			}
		}(i)
	}

	// m receiver
	for i := 0; i < 5; i++ {
		g.Add(1)
		go func() {
			defer g.Done()
			for j := 0; j < 100; j++ {
				select {
				case <-done:
					return
				case v := <-c:
					fmt.Println("receive: ", v)
				}
			}
		}()
	}

	// channel c will be eventually garbage collected if no goroutines reference it any more

	g.Wait()
}
